<?php
/**
 *
 *            ┏┓　　  ┏┓+ +
 *           ┏┛┻━━━━━┛┻┓ + +
 *           ┃         ┃ 　
 *           ┃   ━     ┃ ++ + + +
 *          ████━████  ┃+
 *           ┃　　　　　 ┃ +
 *           ┃　　　┻　　┃
 *           ┃　　　　　 ┃ + +
 *           ┗━┓　　　┏━┛
 *             ┃　　　┃　　　　
 *             ┃　　　┃ + + + +
 *             ┃　　　┃    Code is far away from bug with the alpaca protecting
 *             ┃　　　┃ + 　　　　        神兽保佑,代码无bug
 *             ┃　　　┃
 *             ┃　　　┃　　+
 *             ┃     ┗━━━┓ + +
 *             ┃         ┣┓
 *             ┃ 　　　　　┏┛
 *             ┗┓┓┏━━┳┓┏━┛ + + + +
 *              ┃┫┫  ┃┫┫
 *              ┗┻┛  ┗┻┛+ + + +
 * Created by PhpStorm.
 * User: weaponhsu
 * Date: 2017/1/5
 * Time: 下午5:35
 */

namespace models\DAO;


use ErrorMsg\AbstractErrorMsg;
use Doctrine\DBAL\Driver\PDOStatement;
use Yaf\Exception;
use Yaf\Registry;

class BaseModel{

    private $__conn;        //实例化出对个对应模型
    private $__sql;         //生成查询语句
    private $__field;       //查询的字段

    private $__table;       //设置表格名字
    // 查询参数
    private $__options;
    private $__where;       //条件
    private $__group;       //分组
    private $__having;       //分组
    private $__order;       //设置排序规则
    private $__limit;       //设置获取条数或者分页
    private $__start_time;
    static private $instance;

    /**
     * 构造函数、设置需实例化的数据局表名，以及设置数据库连接，以便后循的连贯操作
     * BaseModel constructor.
     * @param string $table_name
     * @throws Exception
     */
    public function __construct($table_name = '')
    {

        $this->__start_time  = microtime(true);
        $this->__table = !empty($table_name) ? strtolower(trim($table_name)) : '';

        if(empty($this->__table)){
            //抛出致命错误
            throw new Exception(AbstractErrorMsg::BASE_MODEL_EMPTY_TABLE_NAME, AbstractErrorMsg::BASE_MODEL_EMPTY_TABLE_NAME_NO);
        }
        $this->__conn = Registry::get('conn');

    }

    /**
     * @return BaseModel
     * @throws Exception
     */
    static public function getInstance(){
        if(is_null(self::$instance)){

            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * 查询列表数据
     * @param array $condition
     * @return bool|BaseModel|Exception
     * @throws Exception
     * @throws \ReflectionException
     */
    final public function select($condition = []){

        $backtrace = debug_backtrace()[0];
        $this->__options['where']=$condition;

        $this->genSql($backtrace['function']);
        $pdo_statement_obj = $this->__conn->executeQuery($this->__sql);

        $result = $this->__checkPdoStatement($pdo_statement_obj);

        $data =$this->setModelProperty($result?$pdo_statement_obj:[],true);
        return $result === true ? $data : $result;
    }

    /**
     * 查询单条数据
     * @param array $condition      查询条件
     * @return bool|BaseModel|Exception
     * @throws Exception
     * @throws \ReflectionException
     */
    final public function find($condition = []){
        if (empty($condition) && !is_array($condition)) throw new Exception('条件需使用数组');
        $backtrace = debug_backtrace()[0];
        $this->__options['where']=$condition;
        $this->genSql($backtrace['function']);

        $pdo_statement_obj = $this->__conn->executeQuery($this->__sql);

        $result = $this->__checkPdoStatement($pdo_statement_obj);
        $data =$this->setModelProperty($result?$pdo_statement_obj:[]);
        return $result === true ? $data : $result;
    }

    /**
     * 修改数据
     * @param array $condition
     * @return mixed
     * @throws Exception
     * @throws \Exception
     */
    final public function update($condition = []){
        if (empty($condition) || !is_array($condition)) throw new Exception('条件需使用数据');
        if (empty($condition['id']) && empty($this->__where)) throw new Exception('缺少关键条件ID');

        return $this->Data($condition);
    }

    /**
     * 删除数据
     * @param $condition
     * @return mixed
     * @throws \Exception
     */
    final public function delete($condition){
        if (!empty($condition)) $condition = is_array($condition)?( empty($condition[0])?$condition:['id' =>$condition] ): ['id' =>$condition];
        return $this->Data($condition);
    }

    /**
     * 执行原生SQL语句，方法用于执行SQL查询操作,返回查询结果数据集
     * @param $sql
     * @return mixed
     */
    final public function query($sql){
        try{
            $this->__sql =$sql;
            $statement = $this->__conn->executeQuery($this->__sql);
        }catch (Exception $e){
            Registry::get("error_log")->addAlert(json_encode(['time' => microtime(true)-$this->__start_time, 'sql' => $sql]), ['error_log' => $e->getMessage()]);
            return false;
        }

        return $statement->fetchall();
    }

    /**
     * 执行原生SQL语句，方法用于执行SQL查询操作,返回查询结果单条数据
     * @param $sql
     * @return bool|Exception
     * @throws Exception
     */
    final public function execute($sql){

        try{
            $this->__sql =$sql;
            $pdo_statement_obj = $this->__conn->executeQuery($this->__sql);
            $result = $this->__checkPdoStatement($pdo_statement_obj);
        }catch (Exception $e){
            Registry::get("error_log")->addAlert(json_encode(['time' => microtime(true)-$this->__start_time, 'sql' => $sql]), ['error_log' => $e->getMessage()]);
            return false;
        }


        return $result === true ? $pdo_statement_obj->fetch() : $result;
    }

    /**
     * 获取数据库错误
     * @param PDOStatement $pdo_statement_obj
     * @return bool|Exception
     * @throws Exception
     */
    final private function __checkPdoStatement(PDOStatement $pdo_statement_obj){
        //数据库查询出错
        $end_time = microtime(true);
        $db_time = $end_time - $this->__start_time;
        if($pdo_statement_obj->errorCode() !== '00000') {

            Registry::get("error_log")->addAlert(json_encode(['time' => $db_time,'error_result' => $this->__sql]),['error_log' => $pdo_statement_obj->errorInfo()]);
            throw new Exception($pdo_statement_obj->errorInfo(), $pdo_statement_obj->errorCode());
//            return new Exception($pdo_statement_obj->errorInfo(), $pdo_statement_obj->errorCode());
        }
        //查无结果
        if($pdo_statement_obj->rowCount() == 0){
//            return new Exception(AbstractErrorMsg::BASE_MODEL_EMPTY_RESULT, AbstractErrorMsg::BASE_MODEL_EMPTY_RESULT_NO);
            return [];
        }
        Registry::get("db_log")->addInfo(json_encode(['time' => $db_time,'sql' => $this->__sql]));   //保存每次执行的SQL语句作为日志
        return true;
    }

    /**
     * @param string $sqlType
     * @return string
     */
    final public function genSql($sqlType = ''){


        $sqlTypeArr = ['insert','insertAll','update','delete'];     //设置需要执行事务的操作数据
//        call_user_func();
        if (in_array($sqlType,$sqlTypeArr)){

            $data = '';
            if ($sqlType === 'update'){
                //修改
                $data = '';
                if (empty($this->__where)) {
                    $this->__where =' WHERE '.key($this->__options[$sqlType]).' = '.$this->__options[$sqlType][key($this->__options[$sqlType])];unset($this->__options[$sqlType][key($this->__options[$sqlType])]);
                }
                foreach ($this->__options[$sqlType] as $key =>$where){
                    if (is_array($where)){
                        $data .= "`$key`  =  $where[0]";
                    }else
                        $data .= is_integer($where)?" `$key` = ".$where.',':" `$key` = "." '$where' ".',';
                }


            }elseif ($sqlType ==='delete'){
                //删除生成sql 、默认的主键为：ID

                if (!empty($this->__options[$sqlType])){
                    //使用链式方式将条件设置在where，delete并未传入任何参数
                    $id = $this->__options[$sqlType][key($this->__options[$sqlType])];
                    if(count($id)>1 ){
                        //传入的条件是一个要删除的ID数组

                        $data .= 'WHERE '.key($this->__options[$sqlType]) ." in (" .implode(',',$this->__options[$sqlType][key($this->__options[$sqlType])]).')';
                    }else{
                        //传入的条件是一个要删除的数字
                        if (is_array($this->__options[$sqlType])){
                            $valueArr = $this->__options[$sqlType][key($this->__options[$sqlType])];
                            $delValue = is_array($valueArr)?$valueArr[0]:$valueArr;
                            $data .= ' WHERE '.key($this->__options[$sqlType]). ' = ' . $delValue;
                        }else{
                            $data .= ' WHERE '.key($this->__options[$sqlType]). ' = '. $this->__options[$sqlType];
                        }
                    }
                }else{
                    $data = $this->__where;
                }
            }
            else{
                //添加数据:单条/多条
                $cloumn = [];
                $cloumn_arr = [];
                foreach ($this->__options[$sqlType] as $key => $item) {

                    if (is_array($item)){
                        foreach ($item as $k => $i) {
                            $cloumn[] = "`$k`";
                            $cloumn_arr[$key][] = is_integer($i)? $i: "'$i'";
                        }
                    }else{

                        $cloumn[] = "`$key`";
                        $cloumn_arr[] = is_integer($item)? $item: "'$item'";
                    }
                }

                if (!empty($cloumn_arr)){
                    //组装起values 后面的值
                    foreach ($cloumn_arr as $item) {
                        if (is_array($item)){
                            $cloumn_value[] ='('.implode(',',$item).')';
                        }else{
                            $cloumn_value[] ='('.implode(',',$cloumn_arr).')';break;
                        }
                    }
                }
            }

            //根据不同的操作生成不同的SQL语句
            switch ($sqlType){
                case 'update':$this->__sql = sprintf(strtoupper($sqlType)." `$this->__table` SET %s %s",rtrim($data,','),$this->__where);break;
                case 'delete':$this->__sql = sprintf(strtoupper($sqlType)." FROM `$this->__table` %s",$data);break;
                default:
                    $replace = (!empty($this->__options['replace']))?$this->__options['replace']:'';
                    $this->__sql = $replace. sprintf(substr($sqlType,0,6) .' into `'.$this->__table ."` (%s) values %s",implode(',',array_unique($cloumn)),implode(',',$cloumn_value));break;
            }
        }else{
            //搜索条件拼接
            if ($sqlType ==='select'){
                if (empty($this->__field)){
                    $this->field(true);
                }
                if (empty($this->__where)){

                    $this->where($this->__options['where']);
                }
                $this->__sql = strtoupper($sqlType) .' '. $this->__field .' FROM `' . $this->__table.'`'. $this->__where ;

                if (!empty($this->__group)){
                    $this->__sql .= $this->__group;
                }
                if (!empty($this->__having)){
                    $this->__sql .= $this->__having;
                }
                if (!empty($this->__order)){
                    $this->__sql .= $this->__order;
                }
                if (!empty($this->__limit)){
                    $this->__sql .= $this->__limit;
                }
            } elseif ($sqlType === 'find'){


                if (empty($this->__field)){
                    $this->field(true);
                }
                if (!empty($this->__options['where'])){

                    $this->where($this->__options['where']);
                }

                $this->__sql = 'SELECT ' . $this->__field .' FROM `'. $this->__table.'`'. $this->__where ;
                if (empty($this->__limit)){
                    $this->__sql .= ' limit 1';
                }

            }


        }
    }

    /**
     * 设置要查询的数据表字段，默认为* 可传入sting 字符串 或者[] 数组
     * @param $field
     * @return $this
     * @throws \ReflectionException
     */
    final public function field($field){

        if (is_string($field)){

            $this->__field = $field;
        }elseif(is_array($field)){
            //数组转字符串
            $this->__field = implode(',',$field);
        }else{
            $reflection_class = new \ReflectionClass($this);
            $foreach_data = $reflection_class->getProperties();
            $fieldArr = [];
            foreach ($foreach_data as $index =>$property_obj){
                if (stripos($property_obj->getName(),'_') !== false && $property_obj->getName() !== null){
                    $fieldArr[] = '`'.substr($property_obj->getName(),1).'`';
                }
            }
            $this->__field = implode(',',$fieldArr);
        }

        return $this;
    }

    /**
     * 设置where 条件，默认为空，不带任何条件，可传入sting 字符串 或者[] 数组,默认连接方式为：AND 如要用OR 需另外指定
     * @param array $condition
     * @param string $logic
     * @return $this
     */
    final public function where($condition = [],$logic = 'AND'){

        //生成查询语句
        if (!empty($condition)){

            if (is_string($condition) && !empty($condition)){
                $this->__where = ' WHERE '.$condition;
            }elseif(is_array($condition) && !empty($condition)){

                foreach ($condition as $key =>$where){

                    if(!empty($where['logic'])){$where['logic'];unset($where['logic']);}

                    if (is_array($where)){

                        foreach ($where as $cloumn => $value){

                            if (is_array($value)){

                                $this->__where .= " $logic `$key` $cloumn (".implode(',',$value).")";
                            }else
                                //$cloumn   根据传入的连接符进行sql的拼接
                                switch ($cloumn){
                                    case 'in' : $this->__where .= " $logic `$key` $cloumn  ($value)";break;
                                    default:$this->__where .= " $logic `$key` $cloumn = '$value'";
                                }
//
                        }
                    }else{

                        $this->__where .= " $logic `$key` = '$where'";
                    }
                }
                $this->__where = ' WHERE '.ltrim($this->__where," AND");
            }else{
                $this->__where = '';
            }

        }
//        var_dump($this->__where);
        return $this;
    }

    /**
     * GROUP方法也是连贯操作方法之一，通常用于结合合计函数，根据一个或多个列对结果集进行分组 。
     * @param string $group
     * @return $this
     */
    final public function group($group = ''){
        if (is_string($group) && iseet($group)){
            $this->__group = 'group by '.$group;
        }else $this->__group = '';
        return $this;
    }

    /**
     * HAVING方法也是连贯操作之一，用于配合group方法完成从分组的结果中筛选（通常是聚合条件）数据。
     * @param $having
     * @return $this
     */
    final public function having($having){
        if (is_string($having) && iseet($having)){
            $this->__having = 'group by '.$having;
        }else $this->__having = '';
      return $this;
    }

    /**
     * 设置排序方式，默认无排序，可传入sting 字符串 或者[] 数组
     * @param array $order
     * @return $this
     */
    final public function order($order){
        $this->__order = ' order by ';

        //排序格式 单个排序['id'=>'desc'],也可以使用字符串：id desc  多个排序 ['name'=>'desc','id'=>asc]
        if (!empty($order && is_array($order))){
            //循环拼接排序条件
            foreach ($order as $column => $rule){
                $this->__order .= "$column $rule,";
            }

            $this->__order = rtrim($this->__order,',');

        }elseif (is_string($order)){
            $this->__order .= $order;
        }else $this->__order = '';

        return $this;
    }

    /**
     * limit方法也是模型类的连贯操作方法之一，主要用于指定查询和操作的数量.
     * @param $limit
     * @return $this
     */
    final public function limit($start = 1,$size = 15){
        $start = ($start - 1) * $size;
        $this->__limit = " limit $start,$size";
        return $this;
    }

    /**
     * 设置对应model的属性值
     * @param $pdo_statement
     * @param bool $is_array
     * @return $this
     * @throws Exception
     * @throws \ReflectionException
     */
    final private function setModelProperty($pdo_statement, $is_array = false){

        if(empty($pdo_statement))return [];
        if ($is_array === false){
            $record_arr = $pdo_statement instanceof PDOStatement ? $pdo_statement->fetchObject() :
                ($pdo_statement instanceof ArrayStatement ? $pdo_statement->fetch() : new Exception("异常数据类型", "005"));
            if($record_arr instanceof Exception) throw $record_arr;
            foreach (explode(',',$this->__field) as $index =>$property) {
                $this->__set('_'.trim($property,'`'),$record_arr->{trim($property,'`')});
            }
        }else{
            $data_array = [];

            foreach($pdo_statement->fetchAll() as $index => $item){
                $data_array[] = $item;
            }

            $this->__set('data', $data_array);
        }


        return $this;
    }

    /**
     * @param array $data
     * @param bool $replace
     * @return mixed
     * @throws \Exception
     */
    final public function insert(array $data = [], $replace = false){
        if ($replace) $this->__options['replace']='replace';
        return $this->Data($data);
    }

    /**
     * 添加多条数据
     * @param array $data
     * @param bool $replace
     * @throws \Exception
     */
    final public function insertAll(array $data = [], $replace = false){
        return $this->Data($data);
    }


    final public function setInc($field,$step = 1){
        if (empty($this->__where)) {
            // 没有条件不做任何更新
            throw new Exception('no data to update');
        }
        return $this->update([$field=>[$field . '+' . $step]]);

    }

    final public function setDec($field,$step = 1){
        if (empty($this->__where)) {
            // 没有条件不做任何更新
            throw new Exception('no data to update');
        }
        return $this->update([$field=>[$field . '-' . $step]]);

    }

    /**
     * 添加数据统一转入方法
     * @param array $data
     * @return mixed
     * @throws \Exception
     */
    final private function Data(array $data = [])
    {
        $backtrace = debug_backtrace()[1];
        if(empty($data))$this->__options[$backtrace['function']]=$data;
        $this->__options[$backtrace['function']]=$data;
        $this->genSql($backtrace['function']);
        $result = $this->transaction();

        return $result;
    }

    /**
     * 插入数据、修改数据、删除数据时用的事务
     * @return mixed
     * @throws \Exception
     */
    final public function transaction(){

        $this->__conn->beginTransaction();
        $result = null;
        try{
            if (debug_backtrace()[2]['function']==='insert'){
                $this->__conn->executeQuery($this->__sql);       // executeUpdate用于执行 INSERT、UPDATE 或 DELETE 语句
                $result = $this->__conn->lastInsertId();

            }else{
                $result = $this->__conn->executeUpdate($this->__sql);       // executeUpdate用于执行 INSERT、UPDATE 或 DELETE 语句
            }
            Registry::get("db_log")->addInfo(json_encode(['time' => microtime(true)-$this->__start_time, 'sql' => $this->__sql]));
            $this->__conn->commit();
        }catch (\Exception $e){
            Registry::get("error_log")->addAlert(json_encode(['time' => microtime(true)-$this->__start_time, 'sql' => $this->__sql ]), ['error_log' => $e->getMessage()]);
            $this->__conn->rollBack();   //发生错误回滚
            throw $e;
        }

        return $result !== null ? $result : $this->__conn;
    }

    /**
     * 开启事务
     */
    final public function benginTran()
    {
        $this->__conn->beginTransaction();
    }

    /**
     * 提交事务
     */
    final public function commitTran()
    {
        $this->__conn->commit();
    }

    /**
     * 回滚事务
     */
    final public function rollBackTran()
    {
        $this->__conn->rollBack();   //发生错误回滚
    }
}
